package icraf.carbon.gui;

import icraf.carbon.core.Project;
import icraf.carbon.core.ProjectIO;
import icraf.carbon.gui.MenuFactory.MenuProperty;
import icraf.common.util.CommonFileFilter;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.ListModel;
import javax.swing.ProgressMonitor;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

import com.hexiong.jdbf.DBFReader;
import com.hexiong.jdbf.JDBFException;

public class ImportRawDataPanel extends JPanel implements
		PropertyChangeListener {
	int fileType = 1;
	final static int FILE_DBF = 1, FILE_TXT = 2;

	class CopyFiles extends SwingWorker<Void, CopyData> {
		private File srcFile;
		ArrayList<String[]> data = new ArrayList<String[]>();
		long totalBytes = 100;// n lines
		long bytesCopied = 0;
		int progress = 0;

		CopyFiles(File src) {
			this.srcFile = src;
		}

		private void addRow(String[] row) {
			data.add(row);

			int countByte = 0;
			for (int i = 0; i < row.length; i++) {
				try {
					countByte += row[i].getBytes("UTF-8").length;
				} catch (UnsupportedEncodingException e) {
				}
			}
			bytesCopied += countByte;

			progress = (int) ((100 * bytesCopied) / totalBytes);
			CopyData current = new CopyData(progress, srcFile.getName(),
					getTotalKiloBytes(totalBytes),
					getKiloBytesCopied(bytesCopied));
			// progress and fire property change event
			setProgress(progress);

			// publish current progress data for copy task
			publish(current);
		}

		// perform time-consuming copy task in the worker thread
		@Override
		public Void doInBackground() {
			progress = 0;
			// initialize bound property progress (inherited from SwingWorker)
			setProgress(0);
			totalBytes = srcFile.length();
			// determine the scope of the task
			bytesCopied = 0;
			data.clear();
			try {
				int dbfFieldCount = 1;
				long startTimeMs = System.currentTimeMillis( );
				
				if (fileType == FILE_TXT) {

					FileReader dataReader = new FileReader(srcFile);
					BufferedReader in = new BufferedReader(dataReader);
					String str;
					String s;
					String[] row = new String[0];
					while ((s = in.readLine()) != null) {
						str = new String(s.getBytes(), "UTF-8");
						row = str.split("\t");
						addRow(row);
					}
					dbfFieldCount = row.length;
					in.close();
				} else if (fileType == FILE_DBF) {

					DBFReader dbfReader = new DBFReader(
							srcFile.getAbsolutePath());
					

					// define variable
					dbfFieldCount = dbfReader.getFieldCount();
					String header[] = new String[dbfFieldCount];
					// read header
					for (int i = 0; i < dbfFieldCount; i++) {
						header[i] = dbfReader.getField(i).getName();
					}

					addRow(header);
					while (dbfReader.hasNextRecord() && !isCancelled()) {

						Object aobj[] = dbfReader.nextRecord(Charset
								.forName("UTF-8"));
						String[] row = new String[dbfFieldCount];
						for (int j = 0; j < aobj.length; j++) {
							row[j] = aobj[j].toString();
						}

						addRow(row);
					}
				}
				long taskTimeMs  = System.currentTimeMillis( ) - startTimeMs;
				System.out.println(taskTimeMs);
				
				String[][] dataStr = (String[][]) data.toArray(new String[data
						.size()][dbfFieldCount]);
				setData(dataStr);
				setProgress(100);
				showTable();
				setProgress(100);
				
				JOptionPane.showMessageDialog(parentPanel, 
						MenuProperty.PROJECT_IMPORT_DONE.getAccessibleDescription(), 
						MenuProperty.PROJECT_IMPORT_DONE.getLabel(),
						JOptionPane.INFORMATION_MESSAGE);
			} catch (JDBFException e) {
				e.printStackTrace();
			} catch (FileNotFoundException e1) {
				e1.printStackTrace();
			} catch (UnsupportedEncodingException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			}

			return null;
		}

		private long getTotalKiloBytes(long totalBytes) {
			return Math.round(totalBytes / 1024);
		}

		private long getKiloBytesCopied(long bytesCopied) {
			return Math.round(bytesCopied / 1024);
		}

		// process copy task progress data in the event dispatch thread
		@Override
		public void process(List<CopyData> data) {
			if (isCancelled()) {
				return;
			}
			CopyData update = new CopyData(0, "", 0, 0);
			for (CopyData d : data) {
				// progress updates may be batched, so get the most recent
				if (d.getKiloBytesCopied() > update.getKiloBytesCopied()) {
					update = d;
				}
			}

			// update the progress monitor's status note with the
			// latest progress data from the copy operation, and
			// additionally append the note to the console
			String progressNote = update.getKiloBytesCopied() + " of "
					+ update.getTotalKiloBytes() + " kb";
			progressMonitor.setNote(progressNote);
		}

		// perform final updates in the event dispatch thread
		@Override
		public void done() {
			try {
				// call get() to tell us whether the operation completed or
				// was canceled; we don't do anything with this result
				// Void result = get();
				get();
				// console.append("Copy operation completed.\n");
			} catch (InterruptedException e) {

			} catch (CancellationException e) {
				// get() throws CancellationException if background task was
				// canceled
				// console.append("Copy operation canceled.\n");
			} catch (ExecutionException e) {
				// console.append("Exception occurred: " + e.getCause());
			}
			// reset the example app
			// copyButton.setEnabled(true);
			// progressMonitor.setProgress(0);
		}

	}

	class CopyData {
		private int progress;
		private String fileName;
		private long totalKiloBytes;
		private long kiloBytesCopied;

		CopyData(int progress, String fileName, long totalKiloBytes,
				long kiloBytesCopied) {
			this.progress = progress;
			this.fileName = fileName;
			this.totalKiloBytes = totalKiloBytes;
			this.kiloBytesCopied = kiloBytesCopied;
		}

		int getProgress() {
			return progress;
		}

		String getFileName() {
			return fileName;
		}

		long getTotalKiloBytes() {
			return totalKiloBytes;
		}

		long getKiloBytesCopied() {
			return kiloBytesCopied;
		}
	}

	class RawDataTableModel extends MatrixTableModel {

		private static final long serialVersionUID = 8968868974903218229L;
		private String[][] data;
		private NumberRowHeader tableListModel = new NumberRowHeader(this);

		// private String[] columnHeader;

		public RawDataTableModel() {

		}

		@Override
		public Class<?> getColumnClass(int column) {
			return String.class;
		}

		@Override
		public int getColumnCount() {
			if (data != null && data.length > 0) {
				return data[0].length;
			}
			return 0;
		}

		@Override
		public String getColumnName(int col) {
			if (data != null && data.length > 0) {
				return data[0][col];
			}
			return "column_" + Integer.toString(col);
		}

		public String[][] getData() {
			return data;
		}

		@Override
		public int getRowCount() {
			if (data == null)
				return 0;
			return data.length - 1;
		}

		@Override
		public ListModel<Object> getRowHeaderModel() {
			return tableListModel;
		}

		@Override
		public Object getValueAt(int row, int column) {
			return data[row + 1][column];
		}

		@Override
		public boolean isCellEditable(int row, int col) {
			return false;
		}

		public void setData(String[][] data) {
			this.data = data;
			fireTableStructureChanged();
			tableListModel.fireContentChange(0);
		}

		public String[] getColumnHeader() {
			return data[0];
		}
	}

	class HeaderSelector extends MouseAdapter {
		JTable table;
		HeaderEditor editor;
		DefaultTableCellRenderer[] headerRenderer = new DefaultTableCellRenderer[4];

		public HeaderSelector(JTable t) {
			table = t;
			editor = new HeaderEditor(this);
			for (int i = 0; i < 4; i++) {
				headerRenderer[i] = new DefaultTableCellRenderer();
				headerRenderer[i].setBackground(sourceColor[i]);
			}
		}

		private int getColumn(JTableHeader th, Point p) {
			TableColumnModel model = th.getColumnModel();
			for (int col = 0; col < model.getColumnCount(); col++)
				if (th.getHeaderRect(col).contains(p))
					return col;
			return -1;
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			JTableHeader th = (JTableHeader) e.getSource();
			Point p = e.getPoint();
			int col = getColumn(th, p);
			TableColumn column = th.getColumnModel().getColumn(col);
			String oldValue = (String) column.getHeaderValue();
			Object value = editor.showEditor(parentPanel, col, oldValue);
			column.setHeaderValue(value);
			int idx = sourceLabelArr.indexOf(value);
			if (idx >= 0) {
				column.setHeaderRenderer(headerRenderer[idx]);
			} else {
				column.setHeaderRenderer(null);
			}
			th.resizeAndRepaint();
			super.mouseReleased(e);
		}
	}

	String[] sourceLabels = { MenuProperty.PROJECT_IMPORT_INITIAL.getLabel(),
			MenuProperty.PROJECT_IMPORT_SUBSEQUENT.getLabel(),
			MenuProperty.PROJECT_ZONE_PARTITION.getLabel(),
			MenuProperty.PROJECT_ZONE_AREA.getLabel() };
	ArrayList<String> sourceLabelArr = new ArrayList<String>();
	int[] inputColumn = new int[4];
	Color[] sourceColor = { Color.green.brighter(), Color.cyan.brighter(),
			Color.yellow.brighter(), Color.pink };
	String[] headerOrigin;
	ArrayList<String> itemsArr = new ArrayList<String>();
	private CommonFileFilter DBFFilter = new CommonFileFilter(
			"DBF File (*.dbf)", "dbf");
	private CommonFileFilter textFileFilter = new CommonFileFilter(
			"Text (Tab Delimited) (*.txt)", "txt");

	class HeaderEditor {
		HeaderSelector selector;
		String[] items;

		public HeaderEditor(HeaderSelector hs) {
			sourceLabelArr.addAll(Arrays.asList(sourceLabels));
			itemsArr.add("");
			itemsArr.addAll(sourceLabelArr);
			Arrays.fill(inputColumn, -1);
		}

		public Object showEditor(Component parent, int col, String currentValue) {
			boolean select = true;
			if (sourceLabelArr.contains(currentValue)) {
				itemsArr.add(1, headerOrigin[col]);
				select = false;
			}
			items = itemsArr.toArray(new String[itemsArr.size()]);
			items[0] = currentValue;
			String message = MenuProperty.PROJECT_IMPORT_SELECT.getAccessibleDescription()+ " " + (col + 1) + ":";
			Object retVal = JOptionPane.showInputDialog(parent, message,
					MenuProperty.PROJECT_IMPORT_SELECT.getLabel(), 
					JOptionPane.INFORMATION_MESSAGE,
					null, items, items[0]);
			if (retVal == null)
				retVal = currentValue;
			if (retVal != currentValue) {
				itemsArr.remove(retVal);
				if (select) {
					inputColumn[sourceLabelArr.indexOf(retVal)] = col;
				} else {
					itemsArr.add(1, currentValue);
					inputColumn[sourceLabelArr.indexOf(currentValue)] = -1;
				}
			}

			if (!select) {
				itemsArr.remove(headerOrigin[col]);
			}
			return retVal;
		}
	}

	private static final long serialVersionUID = -6267507992297896197L;

	private RawDataTableModel rawDataTableModel;
	private MatrixPanel rawDataTablePanel;
	private JPanel parentPanel;

	public ImportRawDataPanel(JPanel parentPanel) {
		super();
		this.parentPanel = parentPanel;
		rawDataTableModel = new RawDataTableModel();
		rawDataTablePanel = new MatrixPanel(rawDataTableModel);
		JTable table = rawDataTablePanel.getTable();
		table.getTableHeader().addMouseListener(new HeaderSelector(table));

		this.setLayout(new BorderLayout());
		//JPanel headerPanel = new JPanel();
		this.add(new JLabel("<html>"+MenuProperty.PROJECT_IMPORT_SETTING.getAccessibleDescription().replaceAll("\n", "<br>")+"</html>"), BorderLayout.NORTH);
		this.add(rawDataTablePanel);
		final ImportRawDataPanel c = this;
		this.addHierarchyListener(new HierarchyListener() {
	            public void hierarchyChanged(HierarchyEvent e) {
	                Window window = SwingUtilities.getWindowAncestor(c);
	                if (window instanceof Dialog) {
	                    Dialog dialog = (Dialog)window;
	                    if (!dialog.isResizable()) {
	                        dialog.setResizable(true);
	                    }
	                }
	            }
	        });
	}

	public void setData(String[][] data) {
		rawDataTableModel.setData(data);
		headerOrigin = rawDataTableModel.getColumnHeader().clone();
	}

	public void showTable() {
		boolean valid = true;
		int opt = JOptionPane.showConfirmDialog(parentPanel, this,
				MenuProperty.PROJECT_IMPORT_RAW.getLabel(),
				JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
		if (opt == JOptionPane.YES_OPTION) {
			int[] cols = getInputColumn();
			
			for(int c: cols) {
				if(c < 0) {
					valid = false;
					break;
				}
			}
			if(valid)
			try {
				project = ProjectIO.importRawData(project,
						rawDataTableModel.getData(), getInputColumn());
			} catch (ParseException e1) {
				//e1.printStackTrace();
				JOptionPane.showMessageDialog(this, e1.getLocalizedMessage(), MenuProperty.PROJECT_IMPORT_SELECT.getLabel(), JOptionPane.ERROR_MESSAGE);
				valid=false;
			}

		}
		if(!valid) {
			int conf = JOptionPane.showConfirmDialog(this, 
					MenuProperty.PROJECT_IMPORT_SETTING.getAccessibleDescription(), 
					MenuProperty.PROJECT_IMPORT_SETTING.getLabel(), 
					JOptionPane.OK_CANCEL_OPTION,
					JOptionPane.ERROR_MESSAGE);
			if(conf == JOptionPane.OK_OPTION) showTable();
		}
	}

	public TableModel getTableModel() {
		return rawDataTableModel;
	}

	public int[] getInputColumn() {
		return inputColumn;
	}

	private ProgressMonitor progressMonitor;
	private CopyFiles operation;
	private Project project;

	public Project importRawData(Project p) {
		this.project = p;
		JFileChooser fileC = new JFileChooser();
		fileC.addChoosableFileFilter(DBFFilter);
		fileC.addChoosableFileFilter(textFileFilter);
		fileC.setFileFilter(DBFFilter);

		int open = fileC.showOpenDialog(parentPanel);
		if (JFileChooser.APPROVE_OPTION == open) {
			File file = fileC.getSelectedFile();
			String ext = CommonFileFilter.getExtension(file.getName());
			if (ext.equalsIgnoreCase("dbf")) {
				fileType = FILE_DBF;
			} else if (ext.equalsIgnoreCase("txt")) {
				fileType = FILE_TXT;
			}

			progressMonitor = new ProgressMonitor(this,
					MenuProperty.PROJECT_IMPORT_PROGRESS.getLabel()+"...", "", 0, 100);
			progressMonitor.setProgress(0);

			// schedule the copy files operation for execution on a background
			// thread
			operation = new CopyFiles(file);
			// add ProgressMonitorExample as a listener on CopyFiles;
			// of specific interest is the bound property progress
			operation.addPropertyChangeListener(this);
			operation.execute();
		}

		return p;
	}

	// executes in event dispatch thread
	public void propertyChange(PropertyChangeEvent event) {
		// if the operation is finished or has been canceled by
		// the user, take appropriate action
		if (progressMonitor.isCanceled()) {
			operation.cancel(true);
		} else if (event.getPropertyName().equals("progress")) {
			// get the % complete from the progress event
			// and set it on the progress monitor
			int progress = ((Integer) event.getNewValue()).intValue();
			progressMonitor.setProgress(progress);
		}
	}
}